/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package translationTable;

import java.net.URL;
import java.util.*;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
import nanoxml.XMLElement;
import syntaxtree.Counter;

/**
 * This class is a kind of layer which helps to access to the data from the both
 * API JANUS and MADKIT. It is possible to request some information through it.
 *
 * @author Karim Naaji <karim.naaji@gmail.com>
 */
public class Data {

    public final static int JANUS = 0;
    public final static int MADKIT = 1;
    private TranslationTable translationTable;
    private Map<String, String> trClasses = null;
    private URL urlTb = XMLFileStream.class.getClass().getResource("/resources/translation_table.xml");
    private URL urlMk = XMLFileStream.class.getClass().getResource("/resources/madkit_hierarchy.xml");
    private URL urlJs = XMLFileStream.class.getClass().getResource("/resources/janus_hierarchy.xml");
    private URL urlCe = XMLFileStream.class.getClass().getResource("/resources/class_equivalents.xml");
    private static Data instance = null;

    /**
     * Data constructor
     *
     * @param String the absolute or relative path of the translation table
     */
    private Data() {
        translationTable = new TranslationTable(urlTb);
    }
    
    private void initTrClasses(){
        String eq;
        Enumeration childs;
        XMLFileStream stream = new XMLFileStream(urlCe);
        this.trClasses = new HashMap<String, String>();
        XMLElement elem = stream.getRoot();
        XMLElement child;
        Enumeration en = elem.enumerateChildren();

        while (en.hasMoreElements()) {
            elem = (XMLElement) en.nextElement();
            childs = elem.enumerateChildren();
            child = (XMLElement) childs.nextElement();
            this.trClasses.put(elem.getStringAttribute("name"), child.getStringAttribute("name"));
        }
    }

    public String getClassEquivalent(String className) {
        if (this.trClasses == null) {
           this.initTrClasses();
        }
        /*
         * for(Entry<String,ArrayList<String>> e: this.trClasses.entrySet()){
         * System.out.print(e.getKey()+" : "); for(int i=0;
         * i<e.getValue().size();i++) System.out.print(e.getValue().get(i));
         * System.out.println(""); }
         */
        if(this.trClasses.containsKey(className.replaceAll("\\s", "")))
            return this.trClasses.get(className.replaceAll("\\s", ""));
        else return className;
    }
    
    public boolean hasClassEquivalent(String className){
        if(this.trClasses == null){
            this.initTrClasses();            
        }
        return this.trClasses.containsKey(className);
    }

    public static Data getInstance() {
        if (instance == null) {
            instance = new Data();
        }
        return instance;
    }

    /**
     * Get through the corresponding xmlFileStream all the ancestors of a class
     *
     * example : Data d = new Data("TranslationTable.xml"); List<String> s =
     * d.getAncestors("Scheduler", Data.MADKIT); System.out.println(s); result :
     * [Agent, AbstractAgent]
     *
     * @param className the name of the class
     * @param API the API of the class
     * @return the list containing all the ancestors
     * @throws DataException when no result
     * @throws DataUnableToSwitchToCorrectXMLFileStream when no it was unable to
     * find the correct xml stream
     */
    public List<String> getAncestors(String className, int API) throws DataException, DataUnableToSwitchToCorrectXMLFileStream {
        List<String> ancestors = new ArrayList<String>();
        XMLFileStream xmlFileStream = getCorrespondantXMLFileStream(API);
        XMLElement root = xmlFileStream.getRoot();

        String s = className;
        while (!((s = getSuperClass(root, s)).equals("Object"))) {
            ancestors.add(s);
        }
        ancestors.add(s);

        return ancestors;
    }

    /**
     * Constructs an object function corresponding to the parameters The result
     * is null if the method/function does not exist
     *
     * example : Data d = new Data("TranslationTable.xml"); Method f =
     * d.getMethods("write","JTextAreaWriter", Data.MADKIT);
     * System.out.println(f); result : public JTextAreaWriter.write( [char, int,
     * int]) : void
     *
     * @param functionName the name of the function
     * @param className the name of the class
     * @param API the API of the class
     * @return the object function instantiated
     * @throws DataException when no results
     * @throws DataUnableToSwitchToCorrectXMLFileStream when no it was unable to
     * find the correct xml stream
     */
    public Method getMethod(String functionName, String className, int API) throws DataException, DataUnableToSwitchToCorrectXMLFileStream {
        XMLFileStream xmlFileStream = getCorrespondantXMLFileStream(API);
        XMLElement root = xmlFileStream.getRoot();
        return getMethods(root, className, functionName);
    }

    /**
     * Constructs an Array of methods with all the methods of a class
     */
    public List<Method> getAllMethods(String className, int API) throws DataException, DataUnableToSwitchToCorrectXMLFileStream {
        XMLFileStream xmlFileStream = getCorrespondantXMLFileStream(API);
        XMLElement root = xmlFileStream.getRoot();
        List<Method> listOfMethods = new ArrayList<Method>();
        // TODO : fonction privée qui récupère la liste des méthodes d'une classe
        return listOfMethods;
    }

    /**
     * Return the super class of a class on the correspondant API
     *
     * example : Data d = new Data("TranslationTable.xml"); String s =
     * d.getSuperClass("Agent", Data.MADKIT); System.out.println(s); result :
     * AbstractAgent
     *
     * @param className the name of the class
     * @param API the API of the class
     * @return the string corresponding to the superclass
     * @throws DataException when no results
     * @throws DataUnableToSwitchToCorrectXMLFileStream when no it was unable to
     * find the correct xml stream
     */
    public String getSuperClass(String className, int API) throws DataException, DataUnableToSwitchToCorrectXMLFileStream {
        String superClass;
        XMLFileStream xmlFileStream = getCorrespondantXMLFileStream(API);
        XMLElement root = xmlFileStream.getRoot();
        return getSuperClass(root, className);
    }

    /**
     * Return the childrens of a class on the correspondant API
     *
     *
     *
     * @param className the name of the class
     * @param API the API of the class
     * @return An array of all strings corresponding to the childrens
     * @throws DataException when no results
     * @throws DataUnableToSwitchToCorrectXMLFileStream when no it was unable to
     * find the correct xml stream
     */
    /*
     */
    public List<String> getChildren(String className, int API) throws DataException, DataUnableToSwitchToCorrectXMLFileStream {
        XMLFileStream xmlFileStream = getCorrespondantXMLFileStream(API);
        XMLElement root = xmlFileStream.getRoot();
        return getChildren(root, className);
    }

    public List<Method> getConstructor(String className, int API) throws DataUnableToSwitchToCorrectXMLFileStream {
        XMLFileStream xmlFileStream = getCorrespondantXMLFileStream(API);
        XMLElement root = xmlFileStream.getRoot();
        return getConstructor(root, className);
    }

    public boolean isTraductable(Method method) {        
        if (!this.translationTable.canBeTranslated(method)) {
            try {
                List<String> ancestors = this.getAncestors(method.getClassName(), MADKIT);
                if (ancestors != null && ancestors.size() > 0) {
                    int i = 0;
                    while (i < ancestors.size()) {
                        method.setClassName(ancestors.get(i));                        
                        if (this.translationTable.canBeTranslated(method)) {
                            return true;
                        }
                        ++i;
                    }
                }
            } catch (Exception e) {
            }
        }
        return translationTable.canBeTranslated(method);
    }

    public ArrayList<Method> getTranslation(Method method) {
        ArrayList<Method> methods = translationTable.getTranslation(method);
        for(int i=0; i<methods.size(); i++){
            ArrayList<String> tmp = this.getJanusMethodParams(methods.get(i));                       
            methods.get(i).setAttributes(tmp);           
            methods.get(i).setVisibility(this.getVisibility(methods.get(i), JANUS));
            methods.get(i).setReturnType(this.getReturnType(methods.get(i), JANUS));
        }
        return methods;
    }
    
    public String getVisibility(Method method, int API){
        XMLElement root;
        if(API == MADKIT) root = new XMLFileStream(urlMk).getRoot();
        else root = new XMLFileStream(urlJs).getRoot();
        Enumeration elems = root.enumerateChildren();
        XMLElement elem;
        boolean classFound = false;
        while(elems.hasMoreElements() && !classFound){//jelclass
            elem = (XMLElement) elems.nextElement();            
            if(elem.getName().contains("jelclass") && elem.getStringAttribute("type").contains(method.getClassName())){
                classFound = true;
                XMLElement comp;
                Enumeration comps = elem.enumerateChildren();
                while(comps.hasMoreElements()){
                    comp = (XMLElement) comps.nextElement();
                    if(comp.getName().contains("methods")){
                        Enumeration methods = comp.enumerateChildren();
                        XMLElement meth;
                        while(methods.hasMoreElements()){
                            meth = (XMLElement)methods.nextElement();
                            if(meth.getStringAttribute("name").contains(method.getName())){
                                return meth.getStringAttribute("visibility");
                            }
                        }
                    }
                }
            }
        }        
        System.err.println("line "+Counter.line+": WARNING: unable to check the visibility of the method "+method.getName()
                +" in class "+method.getClassName()+" in API "+((API == MADKIT)? "madkit" : "janus"));
        return "";
    }

    public String getPackage(String className) {
        //TODO parcours xml pour obtenir le package
        return null;
    }

    public Method initialiseAttributes(Method m) {
        //TODO initialiser les attributs de la méthode passé en paramètre
        return null;
    }

    /**
     * **
     * Private methods * * *
     */
    private String getSuperClass(XMLElement root, String className) throws DataException {
        String superClass = null;
        Enumeration enumerationRoot = root.enumerateChildren();
        XMLElement rootNodeChild = (XMLElement) (enumerationRoot.nextElement());
        while (enumerationRoot.hasMoreElements()) {
            if (rootNodeChild.getName().equals("jelclass")) {
                String fullType = (String) rootNodeChild.getAttribute("fulltype");
                String splitedString[] = fullType.toString().split("[.]");
                String realType = splitedString[splitedString.length - 1];
                if (realType.equals(className)) {
                    superClass = (String) rootNodeChild.getAttribute("superclass");
                }
            }
            rootNodeChild = (XMLElement) (enumerationRoot.nextElement());
        }

        if (superClass == null) {
            superClass = "Object";
        }

        return superClass;
    }

    /*
     * exemple d'utilisation : List<String> test = getChildren(root, "Object");
     * System.out.println(test.get(1));
     *
     */
    private List<String> getChildren(XMLElement root, String className) {
        List<String> children = new ArrayList<String>();
        Enumeration enumerationRoot = root.enumerateChildren();
        while (enumerationRoot.hasMoreElements()) {
            XMLElement rootNodeChild = (XMLElement) (enumerationRoot.nextElement());
            if (rootNodeChild.getName().equals("jelclass")) {
                if (rootNodeChild.getAttribute("superclass") != null) {
                    if (rootNodeChild.getAttribute("superclass").equals(className)) {
                        children.add((String) rootNodeChild.getAttribute("type"));

                    }
                }
            }
        }
        List<String> pourIterer = new ArrayList<String>();
        pourIterer.addAll(children);
        if (!pourIterer.isEmpty()) {
            for (String s : pourIterer) {
                List<String> childrenOfChildren = getChildren(root, s);

                if (!childrenOfChildren.isEmpty()) {
                    children.addAll(childrenOfChildren);
                }
            }
        }


        return children;
    }

    private List<Method> getConstructor(XMLElement root, String className) {
        List<Method> l = new ArrayList<Method>();

        Enumeration enumerationRoot = root.enumerateChildren();
        while (enumerationRoot.hasMoreElements()) {
            XMLElement rootNodeChild = (XMLElement) (enumerationRoot.nextElement());

            if (rootNodeChild.getName().equals("jelclass")) {
                String fullType = (String) rootNodeChild.getAttribute("fulltype");
                String splitedString[] = fullType.toString().split("[.]");
                String realType = splitedString[splitedString.length - 1];
                if (realType.equals(className)) {
                    Enumeration enumerationClass = rootNodeChild.enumerateChildren();
                    while (enumerationClass.hasMoreElements()) {
                        XMLElement classNodeChild = (XMLElement) (enumerationClass.nextElement());
                        if (classNodeChild.getName().equals("methods")) {
                            Enumeration enumerationMethods = classNodeChild.enumerateChildren();
                            while (enumerationMethods.hasMoreElements()) {
                                XMLElement methodsNodeChild = (XMLElement) (enumerationMethods.nextElement());
                                if (methodsNodeChild.getName().equals("constructor")) {
                                    Enumeration enumerationConstructor = methodsNodeChild.enumerateChildren();
                                    while (enumerationConstructor.hasMoreElements()) {
                                        Method f;
                                        String visibility = (String) methodsNodeChild.getAttribute("visibility");
                                        ArrayList<String> attributes = new ArrayList<String>();

                                        Enumeration enumerationParams = methodsNodeChild.enumerateChildren();

                                        while (enumerationParams.hasMoreElements()) {
                                            XMLElement paramsNodeChild = (XMLElement) (enumerationParams.nextElement());

                                            Enumeration enumerationParam = paramsNodeChild.enumerateChildren();
                                            while (enumerationParam.hasMoreElements()) {
                                                XMLElement paramNodeChild = (XMLElement) (enumerationParam.nextElement());
                                                String paramType = (String) paramNodeChild.getAttribute("type");

                                                attributes.add(paramType);
                                            }
                                        }

                                        f = new Method(className, className, visibility, "", attributes);
                                        l.add(f);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        if (l.isEmpty()) {
            l.add(new Method(className, className, "public", "", new ArrayList<String>()));
        }
        return l;
    }

    private Method getMethods(XMLElement root, String className, String functionName) throws DataException {
        Method f = null;

        Enumeration enumerationRoot = root.enumerateChildren();
        while (enumerationRoot.hasMoreElements()) {
            XMLElement rootNodeChild = (XMLElement) (enumerationRoot.nextElement());

            if (rootNodeChild.getName().equals("jelclass")) {
                String fullType = (String) rootNodeChild.getAttribute("fulltype");
                String splitedString[] = fullType.toString().split("[.]");
                String realType = splitedString[splitedString.length - 1];
                if (realType.equals(className)) {
                    Enumeration enumerationClass = rootNodeChild.enumerateChildren();
                    while (enumerationClass.hasMoreElements()) {
                        XMLElement classNodeChild = (XMLElement) (enumerationClass.nextElement());
                        if (classNodeChild.getName().equals("methods")) {
                            Enumeration enumerationMethods = classNodeChild.enumerateChildren();
                            while (enumerationMethods.hasMoreElements()) {
                                XMLElement methodsNodeChild = (XMLElement) (enumerationMethods.nextElement());
                                if (methodsNodeChild.getName().equals("method")) {
                                    Enumeration enumerationMethod = methodsNodeChild.enumerateChildren();
                                    if (methodsNodeChild.getAttribute("name").equals(functionName)) {
                                        String returnType = (String) methodsNodeChild.getAttribute("type");
                                        String visibility = (String) methodsNodeChild.getAttribute("visibility");
                                        ArrayList<String> attributes = new ArrayList<String>();

                                        Enumeration enumerationParams = methodsNodeChild.enumerateChildren();

                                        while (enumerationParams.hasMoreElements()) {
                                            XMLElement paramsNodeChild = (XMLElement) (enumerationParams.nextElement());

                                            Enumeration enumerationParam = paramsNodeChild.enumerateChildren();
                                            while (enumerationParam.hasMoreElements()) {
                                                XMLElement paramNodeChild = (XMLElement) (enumerationParam.nextElement());
                                                String paramType = (String) paramNodeChild.getAttribute("type");

                                                attributes.add(paramType);
                                            }
                                        }

                                        f = new Method(className, functionName, visibility, returnType, attributes);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        if (f == null) {
            throw new DataException("Inexistant function " + functionName + " with class name " + className + " on this API");
        }

        return f;
    }

    private XMLFileStream getCorrespondantXMLFileStream(int API) throws DataUnableToSwitchToCorrectXMLFileStream {
        XMLFileStream xmlFileStream = null;
        URL url;

        switch (API) {
            case MADKIT:
                xmlFileStream = new XMLFileStream(urlMk);
                break;
            case JANUS:
                xmlFileStream = new XMLFileStream(urlJs);
                break;
        }

        if (xmlFileStream == null) {
            throw new DataUnableToSwitchToCorrectXMLFileStream("Unable to find the correct xml file on this API");
        }

        return xmlFileStream;
    }

    private ArrayList<String> getJanusMethodParams(Method get) {
        ArrayList<String> argTypes = new ArrayList<String>();
        XMLElement root = new XMLFileStream(urlJs).getRoot();
        Enumeration children = root.enumerateChildren();
        while (children.hasMoreElements()) {
            root = (XMLElement) children.nextElement();
            if (root.getName().equals("jelclass") && root.getAttribute("type").equals(get.getClassName())) {
                Enumeration roots = root.enumerateChildren();
                while (roots.hasMoreElements()) {
                    XMLElement method = (XMLElement) roots.nextElement();
                    if (method.getName().equals("methods")) {
                        Enumeration methods = method.enumerateChildren();
                        while (methods.hasMoreElements()) {
                            XMLElement elem = (XMLElement) methods.nextElement();
                            if (elem.getAttribute("name").equals(get.getName())) {
                                Enumeration params = method.enumerateChildren();
                                while (params.hasMoreElements()) {
                                    XMLElement el = (XMLElement) params.nextElement();
                                    if (el.getStringAttribute("name").equals(get.getName())) {
                                        Enumeration comp = el.enumerateChildren();
                                        while (comp.hasMoreElements()) {
                                            XMLElement compo = (XMLElement) comp.nextElement();
                                            if (compo.getName().equals("params")) {

                                                Enumeration args = compo.enumerateChildren();
                                                while (args.hasMoreElements()) {
                                                    XMLElement arg = (XMLElement) args.nextElement();
                                                    argTypes.add(arg.getStringAttribute("type"));                                                    
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        return argTypes;
    }

    public ArrayList<String> getJanusSimplestConstructorArguments(String typeDesired) {
        typeDesired = typeDesired.replaceAll("\\s","");        
        int nbArgs = 0;
        int minNbArgs = 10;
        int simpliest = 0;
        int index = 0;
        ArrayList<ArrayList<String>> argTypes = new ArrayList<ArrayList<String>>();
        XMLElement root = new XMLFileStream(urlJs).getRoot();
        Enumeration children = root.enumerateChildren();
        while (children.hasMoreElements()) {
            root = (XMLElement) children.nextElement();
            if (root.getName().equals("jelclass") && root.getStringAttribute("type").equals(typeDesired)) {
                Enumeration roots = root.enumerateChildren();
                while (roots.hasMoreElements()) {
                    XMLElement method = (XMLElement) roots.nextElement();                    
                    if (method.getName().equals("methods")) {
                        Enumeration methods = method.enumerateChildren();
                        while (methods.hasMoreElements()) {
                            nbArgs = 0;
                            XMLElement elem = (XMLElement) methods.nextElement();

                            if (elem.getName().equals("constructor")) {

                                argTypes.add(new ArrayList<String>());
                                Enumeration params = method.enumerateChildren();
                                XMLElement el = (XMLElement) params.nextElement();
                                if (el.getStringAttribute("name").equals(typeDesired)) {
                                    while (params.hasMoreElements()) {
                                        Enumeration comp = el.enumerateChildren();
                                        while (comp.hasMoreElements()) {
                                            XMLElement compo = (XMLElement) comp.nextElement();
                                            if (compo.getName().equals("params")) {
                                                Enumeration args = compo.enumerateChildren();
                                                while (args.hasMoreElements()) {
                                                    XMLElement arg = (XMLElement) args.nextElement();
                                                    argTypes.get(index).add(arg.getStringAttribute("type"));
                                                    ++nbArgs;
                                                }
                                                if (nbArgs < minNbArgs) {
                                                    simpliest = index;
                                                    minNbArgs = nbArgs;
                                                }
                                            }
                                        }
                                    }
                                }
                                ++index;
                            }
                        }
                    }
                }
            }
        }
        if (!argTypes.isEmpty()) {
            return argTypes.get(simpliest);
        } else {
            System.err.println("line " + Counter.getInstance().line + ": WARNING: Correct constructor was not found for the class " + typeDesired + "."
                    + " Arguments may lack in the constructor call.");
            return null;
        }
    }

    private String getReturnType(Method method, int API) {
        XMLElement root;        
        method.setClassName(method.getClassName().replaceAll("\\s", ""));
        method.setName(method.getName().replaceAll("\\s", ""));
        if(API == MADKIT) root = new XMLFileStream(urlMk).getRoot();
        else root = new XMLFileStream(urlJs).getRoot();
        Enumeration elems = root.enumerateChildren();
        XMLElement elem;
        boolean classFound = false;
        while(elems.hasMoreElements() && !classFound){//jelclass
            elem = (XMLElement) elems.nextElement();            
            if(elem.getName().contains("jelclass") && elem.getStringAttribute("type").equals(method.getClassName())){                
                classFound = true;
                XMLElement comp;
                Enumeration comps = elem.enumerateChildren();
                while(comps.hasMoreElements()){
                    comp = (XMLElement) comps.nextElement();
                    if(comp.getName().contains("methods")){                        
                        Enumeration methods = comp.enumerateChildren();
                        XMLElement meth;
                        while(methods.hasMoreElements()){
                            meth = (XMLElement)methods.nextElement();                            
                            if(meth.getStringAttribute("name").contains(method.getName())){                                
                                return meth.getStringAttribute("type");
                            }
                        }
                    }
                }
            }
        }                
        return "";
    }

    public String getPath(String translatedClass, int API) {
        XMLElement root;        
        translatedClass = translatedClass.replaceAll("\\s", "");
        if(API == MADKIT) root = new XMLFileStream(urlMk).getRoot();
        else root = new XMLFileStream(urlJs).getRoot();
        Enumeration elems = root.enumerateChildren();
        XMLElement elem;
        boolean classFound = false;
        while(elems.hasMoreElements() && !classFound){//jelclass
            elem = (XMLElement) elems.nextElement();                      
            if(elem.getName().contains("jelclass") && elem.getStringAttribute("type").equals(translatedClass)){                
                return elem.getStringAttribute("fulltype");
            }
        }                        
        return translatedClass;
    }

   
    /**
     * **
     * Data Exception classes * * *
     */
    public class DataException extends Exception {

        public DataException(String errMesg) {
            super(errMesg);
        }
    }

    public class DataUnableToSwitchToCorrectXMLFileStream extends Exception {

        public DataUnableToSwitchToCorrectXMLFileStream(String errMesg) {
            super(errMesg);
        }
    }
}
